package com.example.demo.util;

import com.example.demo.DemoApplication;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import static com.example.demo.constant.ApplicationConstant.*;

/**
 * 自定义类加载器
 */

@Slf4j
public class MyClassLoader extends ClassLoader {

    //module classes xxx.xxx.xxx.className
    private  static List<String> classes = new LinkedList<>();
    //class path
    private  static String classPath;
    //排除不需要自定加载器加载的类
    private  static List<String> classFilter = new LinkedList<>();
    // class loader name
    private String name = MyClassLoader.class.getName();

    public MyClassLoader(){ }

    public static void loadClassPath() {
        MyClassLoader loader = new MyClassLoader();
        try {
            classPath = MyClassLoader.class.getResource(LINUX_SEQ).toURI().getPath();
        } catch (URISyntaxException e) {
            log.error("Get class path error", e);
        }
        //扫描类
        classFilter.add(DemoApplication.class.getName());
        classFilter.add(MyClassLoader.class.getName());
        loader.findClass();
        //加载类
        for (String name : classes) {
            try {
                loader.loadClass(name);
            } catch (ClassNotFoundException e) {
                log.info("load classes error", e);
            }
        }
        log.info("load classes finish");
    }

    private void findClass() {
        if (classPath == null) {
            return;
        }
        //初始化栈
        Stack<File> stack = new Stack<>();
        stack.push(new File(classPath));

        while (!stack.empty()) {
            File file = stack.pop();
            File[] files = file.listFiles();

            if (files == null) {
                continue;
            }

            for (File f : files) {

                if (f.isDirectory()) {
                    stack.push(f);
                } else if (f.getName().endsWith(FILE_TYPE)) {
                    //类文件路径换成类的全限定名
                    String path = f.toURI().getPath();
                    //文件在class path的路径
                    String clazzStr = path.substring(classPath.length());
                    String classFile = clazzStr.replaceAll(LINUX_SEQ, PACK_SEQ).substring(0, clazzStr.lastIndexOf(FILE_TYPE));
                    if (!classFilter.contains(classFile)) {
                        classes.add(classFile);
                    }
                }
            }
        }
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(name);
        //null表示此类未加载
        if (clazz == null) {
            if (!classes.contains(name)) {
                //由父类加载器加载
                clazz = super.loadClass(name);
            } else {
                //自定义加载器加载
                clazz = load(name);
            }
        }
        return clazz;
    }

    /**
     * 自定义类加载器加载class文件到jvm
     */
    private Class<?> load(String name) throws ClassFormatError{
        Class<?> clazz = findLoadedClass(name);
        String packageName = name.substring(0, name.lastIndexOf("."));
        String fileName = name.substring(name.lastIndexOf(".") + 1).concat(FILE_TYPE);
        String filePath = packageName.replaceAll(PACK_SEQ, LINUX_SEQ).concat(LINUX_SEQ).concat(fileName);
        try (InputStream fileInputStream = new FileInputStream(classPath + filePath)) {
            byte[] b = new byte[fileInputStream.available()];
            fileInputStream.read(b);
            clazz = defineClass(name, b, 0, b.length);
        } catch (IOException e) {
            log.error("Get class input stream error", e);
        }
        return clazz;
    }

    public String getClassPath() {
        return classPath;
    }

    public void setClassPath(String classPath) {
        MyClassLoader.classPath = classPath;
    }

    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
